<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      #canvas {
        border: 1px solid rgba(200, 200, 200, 1);
        background-color: #1b1b1f;
      }
    </style>
  </head>
  <body>
    <canvas id="canvas" width="650" height="550"></canvas>
    <script type="text/javascript">
      let canvas, ctx;

      class Point {
        constructor() {
          this.r = 4;
          this.x = getRandom(0, canvas.width - this.r / 2);
          this.y = getRandom(0, canvas.height - this.r / 2);
          this.speedX = getRandom(-50, 50);
          this.speedY = getRandom(-50, 50);
          this.lastMoveTime = null;
        }

        draw() {
          if (this.lastMoveTime) {
            const now = Date.now();
            const deltaTime = (now - this.lastMoveTime) / 1000;
            this.x += this.speedX * deltaTime;
            this.y += this.speedY * deltaTime;

            if (this.x <= 0 || this.x >= canvas.width) {
              this.speedX = -this.speedX;
            }
            if (this.y <= 0 || this.y >= canvas.height) {
              this.speedY = -this.speedY;
            }
          }
          ctx.beginPath();
          ctx.fillStyle = "rgb(200, 200, 200)";
          ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
          ctx.fill();
          this.lastMoveTime = Date.now();
        }
      }

      class Grap {
        constructor(pointNumber = 15, maxDistance = 200) {
          this.points = new Array(pointNumber).fill(0).map(() => new Point());
          this.maxDistance = maxDistance;
          this.animationId = undefined;
        }

        draw() {
          if (!canvas) return;
          // 清除之前的画布
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          // 清除动画帧
          if (this.animationId) cancelAnimationFrame(this.animationId);

          this.animationId = requestAnimationFrame(() => this.draw());

          const lengths = this.points.length;
          for (let i = 0; i < lengths; i++) {
            // 绘制点
            this.points[i].draw();
            for (let j = i + 1; j < lengths; j++) {
              const distance = Math.sqrt(
                Math.pow(this.points[i].x - this.points[j].x, 2) +
                  Math.pow(this.points[i].y - this.points[j].y, 2)
              );

              // 绘制线
              ctx.beginPath();
              ctx.moveTo(this.points[i].x, this.points[i].y);
              ctx.lineTo(this.points[j].x, this.points[j].y);
              ctx.closePath();
              ctx.strokeStyle = `rgba(200, 200, 200, ${
                1 - distance / this.maxDistance
              })`;
              ctx.stroke();
            }
          }
        }
      }

      // 随机坐标
      function getRandom(min, max) {
        return Math.floor(Math.random() * (max - min + 1) + min);
      }

      function init() {
        canvas = document.getElementById("canvas");
        ctx = canvas.getContext("2d");
        const grap = new Grap();
        grap.draw();
      }

      init();
    </script>
  </body>
</html>
